#include "bcd.h"

#include <string>
#include <cstdlib>

namespace ZioUtils
{

#pragma region ConstructorsAndDestructors

	// Conversion Constructor from a string
	bcd::bcd(const char* number_string) 
		: m_data(NULL), m_length(0)
	{
		//std::cout << "bcd conversion constructor" << std::endl;
		
		// CHECK THIS CODE! CRASH ! Andrea.
		if (number_string == NULL) 
			return;

		AllocMemory(strlen(number_string));

		for (Int64 i = m_length - 1; i >= 0; i--)
		{
			char digitChar = number_string[i];
			UInt8 digit = digitChar - 48;
			m_data[m_length - i - 1] = digit;
		}
	}

	// Conversion Constructor from a number
	bcd::bcd(const UInt64 number)
		:	m_data(NULL), m_length(0)
	{
		UInt64 num = number;

		UInt32 count = 0;
		for (UInt64 testDigits = number; testDigits > 0; testDigits /= 10)
			++count;

		count = std::max<UInt>(count, 1);

		AllocMemory(count);

		// Init Data
		for (UInt32 i = 0; i < m_length; i++)
		{
			UInt8 digit = num % 10;
			num /= 10;

			m_data[i] = digit;
		}		
	}

	// Copy Constructor
	bcd::bcd(const bcd& copy)
		:	m_data(NULL), m_length(0)
	{
		//std::cout << "bcd copy constructor" << std::endl;

		AllocMemory(copy.m_length);
		memcpy(m_data, copy.m_data, copy.m_length);
	}

	bcd::~bcd()
	{
		//std::cout << "bcd destructor" << std::endl;
		free(m_data);		// Should always be valid
	}

#pragma endregion ConstructorsAndDestructors

#pragma region GetMethods

	UInt32 bcd::NumberOfDigits() const
	{
		return m_length;
	}

	UInt8 bcd::GetDigit(const UInt32 i) const
	{
		assert(i < m_length);
		return m_data[i];
	}

	UInt64 bcd::GetValue() const
	{
		UInt64 value = 0;

		UInt32 maxLength = (m_length < 20) ? m_length : 19;
		for (UInt32 i = 0; i < maxLength; i++)
		{
			UInt64 exp = 1;
			for (Int32 k = i; k > 0; --k) exp *= 10;
			value += GetDigit(i) * exp;
		}

		return value;
	}

#pragma endregion GetMethods

#pragma region Operators

	// Assignment
	bcd& bcd::operator=(const bcd& other)
	{
		if (this != &other)
		{
			// Deep copy
			AllocMemory(other.m_length);
			memcpy(m_data, other.m_data, other.m_length);
		}

		return *this;
	}

	// Addition
	bcd& bcd::operator += (const bcd& other)
	{
		// Realloc if needed
		if (other.m_length > m_length)
		{
			UInt32 diff = other.m_length - m_length;
			ResizeMemory(diff);
		}

		UInt8 overflow = 0;
		for (UInt32 i = 0; i < m_length; i++)
		{
			UInt32 sum = m_data[i] + overflow;
			
			if (i < other.m_length)
				sum += other.m_data[i];

			m_data[i] = (sum >= 10) ? static_cast<UInt8>(sum - 10) : static_cast<UInt8>(sum);
			overflow = (sum >= 10) ? 1 : 0;

			// Need to alloc space for 1 more digit?
			if (i == m_length - 1 && overflow > 0)
			{
				ResizeMemory(1);
				//m_data[m_length] = overflow;
			}
		}

		// 	for (Int64 i = 0; i < m_length; i++)
		// 	{
		// 		std::cout << "m_data[" << i << "] = " << static_cast<unsigned short>(m_data[i]) << std::endl;
		// 	}

		return *this;
	}

	bcd bcd::operator+(const bcd& other)
	{
		bcd sum = *this;
		sum += other;
		return sum;
	}

	bcd& bcd::operator*=(const bcd& other)
	{
		bcd copy = *this;
		UInt64 copyValue = other.GetValue();

		while (--copyValue > 0)
		{
			*this += copy;
		}

		return *this;
	}

#pragma endregion Operators

#pragma region FriendAndUtilityFunctions

	/* Allocates size bytes to store data. Old data (if present) is copied into the new array */
	void bcd::AllocMemory(UInt32 size)
	{
		assert(size > 0);
		UInt8* oldData = m_data;


		m_length = size;
		m_data = static_cast<UInt8*>(malloc(sizeof(UInt8) * m_length));
		assert(m_data);
		memset(m_data, 0, m_length);

		if (oldData != NULL) 
			free(oldData);
	}

	void bcd::ResizeMemory(UInt32 size)
	{
		assert(size > 0);
		m_data = static_cast<UInt8*>(realloc(m_data, m_length + size));
		assert(m_data);
		memset(m_data + m_length, 0, size); // init new mem
		//for (int i = m_length; i < m_length + size; i++)
		//	m_data[i] = 0;
		m_length += size;
	}

	std::ostream& operator<<(std::ostream& out, const bcd& number)
	{
		for (Int64 i = number.m_length - 1; i >= 0; i--)
		{
			out << static_cast<unsigned short>(number.m_data[i]);				// Need to cast to avoid ascii encoding
		}

		return out;
	}

#pragma endregion FriendAndUtilityFunctions

}

